﻿using System;
using System.Collections;
using System.Collections.Generic; 
using System.Text;
using System.IO; 
/* inspired by http://www.dina.dk/~sestoft/gcsharp/Parsers.cs 
 * purely functional (no mutations except the underlying stream) 
 * has potential to 
 */
namespace Parseq.Core
{
    /*
     * parsing - I can use while to handle the loop of parsing... 
     * 
     * the returning of data will have to be bound together, or use the type object. 
     * always & fail
     * 
     * Result will be the binding together thing - it will hold any type?? 
     * 
     * Parse(Input) -> Result -> this is the basic structure of the Parsing. 
     * Let's go for it! 
     * 
     * we'll separate the highest level of the parser
     */



    public class SequenceParser : ListParser
    {
        public SequenceParser(BaseParser[] sequence) : base(sequence)
        {
        }

        public override Result InnerParse(IReader reader, Location pos) 
        {
            object[] results = new object[this.Count];
            Location newPos = pos;
            Result currentResult;
            for (int i = 0; i < results.Length; ++i)
            {
                currentResult = ((BaseParser)this[i]).Parse(reader, newPos);
                if (Result.Failed(currentResult))
                {
                    return Result.MakeFail(reader, pos);
                }
                else
                {
                    newPos = currentResult.Position;
                    results[i] = currentResult.Inner;
                }
            }
            return new Result(results, newPos);
        }
        public override string ToString()
        {
            return string.Format("<SequenceParser:{0}>", Util.String.ArrayToString(this.ToArray(typeof(BaseParser))));
        }
    }

    public class ChoiceParser : ListParser 
    {
        public ChoiceParser(BaseParser[] choices) : base(choices)
        {
        }

        public override Result InnerParse(IReader reader, Location pos)
        {
            Result result;
            for (int i = 0; i < this.Count ; ++i)
            {
                result = ((BaseParser)this[i]).Parse(reader, pos); // okay - this is where we are doing *backtracking* for the first time! 
                if (Result.Succeeded(result))
                    return result;
            }
            return Result.MakeFail(reader, pos);
        }

        public override string ToString()
        {
            return string.Format("<ChoiceParser:{0}>", Util.String.ArrayToString(this.ToArray(typeof(BaseParser)))); 
        }
    }

    public class RepeatParser : BaseParser
    {
        public static readonly long INFINITE = -1;

        protected BaseParser inner;
        protected long min;
        protected long max = INFINITE;
        protected object defaultResult = new object[0];
        protected BaseParser lazyInner; // if specified this now becomes a lazy parser. 
        public RepeatParser(BaseParser inner, long min, long max, object defaultResult, BaseParser lazyInner)
        {
            this.inner = inner;
            this.min = min;
            this.max = max;
            this.defaultResult = defaultResult;
            this.lazyInner = lazyInner; 
        }

        public override Result InnerParse(IReader reader, Location pos)
        {
            ArrayList list = new ArrayList(); 
            if (this.lazyInner == null) // this is the eager part.
            {
                return this.eagerParse(reader, pos, this.min, this.max, list);
            }
            else
            {
                Result minParsed = this.eagerParse(reader, pos, this.min, this.min, list);
                if (Result.Failed(minParsed))
                    return minParsed; 
                return this.lazyParse(reader, minParsed.Position, this.min, this.max, list);
            }
        }

        private Result eagerParse(IReader reader, Location pos, long min, long max, ArrayList list)
        {
            Location newPos = pos;
            long count = 0;
            Result result = new Result(new object[0], newPos); // for min = 0 - what does it mean? it means we start with 

            while (checkIfRepeat(count, max, result)) 
            {
                result = this.inner.Parse(reader, newPos);
                if (Result.Succeeded(result))
                {
                    list.Add(result.Inner);
                    count++; // increment count. 
                    newPos = result.Position;
                }
            }
            if (count < min) // when min is zero we'll fail this... 
            { // need to achieve minimum of min times. 
                return new Result(this.defaultResult, pos);
            }
            else
            {
                return new Result(list.ToArray(), newPos);
            }
        }

        private static bool checkIfRepeat(long count, long max, Result r) {
            /*
             * the condition under which we want to enter the loop:
             * 1 - when we have not yet read the minimum and we have not yet failed.
             * 2 - when we have not yet read the maximum and we have not yet failed. 
             * 3 - if maximum == INFINITE, then we will stop only when we have failed. 
             * 4 - if minimum = zero, we should not enter into the reading at all. 
             * 
             * the above logic can be merged together as long as maximum is greater than minimum, into
             * 1 - when we have not yet read the maximum and we have not yet failed. => 
             * 2 - when minimum is zero we do not read => when minimum is not zero. 
             * 3 - when maximum is infinite we stop only when we have failed. 
             * 
             */
            return (max != 0 && ((max == INFINITE && Result.Succeeded(r)) || (count < max && Result.Succeeded(r))));
        }

        private Result lazyParse(IReader reader, Location pos, long min, long max, ArrayList list)
        {
            Location newPos = pos;
            long count = min;
            Result nextParsed, maxParsed;
            do
            {
                nextParsed = this.lazyInner.Parse(reader, newPos);
                if (Result.Succeeded(nextParsed))
                { // we are done. 
                    list.Add(nextParsed.Inner);
                    return new Result(list.ToArray(), nextParsed.Position);
                }
                else
                { // we will 
                    maxParsed = this.inner.Parse(reader, newPos);
                    if (Result.Succeeded(maxParsed))
                    {
                        list.Add(maxParsed.Inner);
                        count++;
                        newPos = maxParsed.Position;
                    }
                }
            } while (checkIfRepeat(count, max, maxParsed));
            return new Result(list.ToArray(), newPos);
        }

        public override string ToString()
        {
            return string.Format("<RepeatParser:{0}>", this.inner);
        }
    }

    public class ZeroOneParser : RepeatParser
    {
        public ZeroOneParser(BaseParser inner, object defaultResult) : base(inner, 0, 1, defaultResult, null) 
        {
        }

        public override Result InnerParse(IReader reader, Location pos) 
        {
            Result temp = base.InnerParse(reader, pos);
            if (Result.Succeeded(temp))
            {
                if (((object[])(temp.Inner)).Length == 0) // this is where things gets returned as zero. 
                {
                    return new Result(this.defaultResult, temp.Position); 
                }
                else
                {
                    return new Result(((object[])temp.Inner)[0], temp.Position);
                }
            }
            else
            {
                return temp; 
            }
        }

        public override string ToString()
        {
            return string.Format("<ZeroOneParser:{0},{0}>", this.inner, this.defaultResult); 
        }
    }
}

